Перейти к основному содержимому

1.21. Языки запросов

Всем

Языки запросов

Язык запросов — это средство взаимодействия между человеком (или программой) и хранилищем данных. Его задача — выразить информационную потребность в форме, понятной системе, и получить ответ: выборку, модификацию, агрегацию или метаинформацию. Важно понимать: язык запросов не является универсальным инструментом. Он проектируется под определённую модель данных и архитектурные ограничения хранилища. Это объясняет разнообразие языков: SQL для реляционных таблиц, XPath для древовидных структур, SPARQL для графов знаний и так далее.

В информационных системах запрос — это формализованное выражение намерения. Оно может быть простым («найти все записи, где поле status равно completed») и сложным («для каждого пользователя вычислить среднее время между событиями login и logout, учитывая только дни, когда он совершал больше трёх операций»). Без языка, поддерживающего такую выразительность и точность, автоматизация обработки данных была бы невозможна.

Современные языки запросов выполняют три ключевые функции:

  1. Извлечение — выборка данных по заданным критериям;
  2. Модификация — вставка, обновление, удаление записей;
  3. Анализ — агрегация, сортировка, группировка, расчёт производных значений.

Эти функции реализуются с учётом физических свойств хранилища: способа индексации, распределённости, транзакционности и семантической структуры данных. Поэтому язык запросов — это отражение внутренней организации системы.

Как это работает

Когда вы формулируете запрос, он проходит несколько этапов обработки. Это особенно заметно в СУБД, но принципиально применимо ко всем системам: от Elasticsearch до XQuery-процессоров.

  1. Парсинг — синтаксический разбор запроса, проверка на соответствие грамматике языка.
  2. Валидация — семантический анализ: существуют ли указанные сущности (таблицы, поля, пути), разрешены ли операции, совместимы ли типы.
  3. Оптимизация — построение плана выполнения. Система оценивает несколько стратегий доступа к данным (например, использовать ли индекс или выполнить полный перебор), выбирает наименее затратную по времени и ресурсам.
  4. Выполнение — фактическое обращение к хранилищу, извлечение или изменение данных.
  5. Формирование результата — сериализация ответа в ожидаемый формат (таблица, JSON, XML, поток событий).

Язык запросов не определяет как именно будет происходить выполнение — это зона ответственности движка. Он определяет что требуется. Это принцип декларативности: вы описываете желаемый результат, а система решает, как его достичь. Именно поэтому SQL считается декларативным языком, в отличие от процедурных языков программирования, где вы последовательно описываете шаги.

Классификация языков запросов по модели данных

Разнообразие языков запросов обусловлено разнообразием моделей данных. Ниже рассматриваются основные классы.

1. Языки для реляционных баз данных

Реляционная модель, предложенная Эдгаром Коддом в 1970 году, строится на основе отношений — математических понятий, реализуемых как таблицы: строки (кортежи) и столбцы (атрибуты). У каждой таблицы есть строго определённая схема, ограничения целостности (первичные и внешние ключи) и операции, соответствующие реляционной алгебре.

Structured Query Language (SQL) — стандарт де-факто для реляционных СУБД. Он включает в себя подъязыки:

  • DDL (Data Definition Language) — определение структур: CREATE TABLE, ALTER INDEX, DROP VIEW;
  • DML (Data Manipulation Language) — манипулирование данными: SELECT, INSERT, UPDATE, DELETE;
  • DCL (Data Control Language) — управление доступом: GRANT, REVOKE;
  • TCL (Transaction Control Language) — управление транзакциями: BEGIN, COMMIT, ROLLBACK.

SQL поддерживает вложенные запросы, соединения таблиц (JOIN), агрегатные функции (COUNT, SUM, AVG), оконные функции, Common Table Expressions (CTE) и многое другое. Его сила — в выразительности при работе со структурированными, нормализованными данными, где связи между сущностями явно заданы.

Несмотря на стандарт ANSI SQL, реализации в разных СУБД (PostgreSQL, Microsoft SQL Server, Oracle, MySQL) имеют собственные расширения и особенности синтаксиса. Это не делает SQL фрагментированным, но требует учёта диалекта при переносе запросов.

SQL-подобные языки в NoSQL-мире

С появлением масштабируемых распределённых систем возникла потребность в сохранении привычной выразительности SQL при отказе от жёсткой схемы и ACID-гарантий. Результатом стали SQL-подобные языки, адаптированные под специфику хранилища:

  • CQL (Cassandra Query Language) — разработан для Apache Cassandra. Поддерживает SELECT, INSERT, UPDATE, но без JOIN и подзапросов, поскольку данные распределены по узлам, и соединения между партициями экономически невыгодны. Вместо этого акцент сделан на эффективный выбор по ключам партиционирования и кластеризации.
  • N1QL (pronounced «nickel») — используется в Couchbase. Позволяет выполнять запросы к JSON-документам, как если бы они были реляционными строками: поддерживает JOIN (даже между документами), вложенные структуры, индексы на полях. Это демонстрирует эволюцию: документоориентированные СУБД постепенно вбирают лучшие практики реляционного мира.
  • LINQ (Language Integrated Query) — синтаксическое расширение языков .NET (C#, VB.NET). Запросы пишутся прямо в коде с помощью ключевых слов from, where, select. На этапе компиляции они транслируются в вызовы методов или в SQL (через Entity Framework). Это повышает безопасность (исключает SQL-инъекции) и типовую строгость.

Эти языки подтверждают: синтаксис SQL стал культурной нормой, и новые системы стремятся к совместимости с ним — даже если их внутреннее устройство радикально иное.

2. Языки для иерархических и полуструктурированных данных

XML и JSON — это модели данных. Они поддерживают вложенность, повторяющиеся элементы, необязательные поля — в отличие от плоской реляционной таблицы. Поэтому для них требуются специализированные средства навигации.

XPath

XPath (XML Path Language) — это язык выражений, позволяющий адресовать узлы в XML-документе. Он работает по принципу файловой системы: путь /bookstore/book/title означает «все элементы title, находящиеся внутри book, которые находятся внутри корневого элемента bookstore». XPath поддерживает:

  • оси навигации (child::, parent::, following-sibling::);
  • предикаты (/book[price > 10]);
  • функции (count(), contains(), normalize-space());
  • операторы сравнения и логические связки.

XPath не предназначен для модификации — только для выборки и вычисления значений. Он встроен в XSLT, XQuery и многие языки программирования (через DOM-процессоры).

XQuery

XQuery — полноценный язык запросов к коллекциям XML-документов. Его часто называют «SQL для XML». Он позволяет:

  • извлекать подмножества документов;
  • трансформировать структуру (например, конвертировать XML в HTML или другой XML-документ);
  • выполнять соединения между документами по ключам;
  • использовать FLWOR-выражения (аналог SELECT-FROM-WHERE в SQL: for, let, where, order by, return).

XQuery поддерживает типизацию через XML Schema (XSD) и может работать с большими объёмами данных в системах, таких как BaseX, eXist-db или Oracle XML DB.

JSONPath

JSONPath — неофициальный, но широко принятый аналог XPath для JSON. Его синтаксис вдохновлён JavaScript: $.store.book[*].title выбирает заголовки всех книг в магазине. Он поддерживает:

  • рекурсивный спуск (..);
  • фильтрацию по условию ([?(@.price < 10)]);
  • выбор по индексу ([0], [-1]);
  • объединение путей (;).

Строгого стандарта JSONPath нет: реализации в Python (jsonpath-ng), JavaScript (jsonpath-plus), .NET (Newtonsoft.Json) могут незначительно различаться. Для более мощных задач (агрегация, трансформация) используется JMESPath — более формализованный и выразительный язык, принятый в AWS CLI, Ansible и других инструментах.

Эти языки особенно важны в интеграционных сценариях: когда данные поступают в виде API-ответов (обычно JSON), и требуется извлечь конкретное поле для дальнейшей обработки — без десериализации всего объекта в память.


3. Языки для графовых баз данных

Графовая модель данных отходит от табличного и иерархического представления. Её основные элементы — узлы (сущности), рёбра (отношения между сущностями) и, необязательно, свойства у обоих. Отношения здесь явные, именованные и направленные. Это позволяет эффективно моделировать системы, где важна не только сущность, но и её связь с другими: социальные сети, знания (онтологии), маршрутизация, цепочки поставок, системы рекомендаций.

Язык запросов к графу должен позволять оперировать паттернами связей. В отличие от SQL, где соединение таблиц — это отложенная операция, в графовых языках соединение — базовая единица выражения.

SPARQL

SPARQL (произносится «sparkle») — стандартный язык запросов для RDF-графов (Resource Description Framework). RDF — это основа семантической паутины: данные выражаются в виде триплетов субъект-предикат-объект. Например:
<Человек123> <знать> <Человек456>,
<Человек123> <имя> "Тимур".

SPARQL построен на синтаксисе, близком к SQL, но вместо FROM table используется WHERE { шаблон триплетов }. Запрос описывает паттерн графа, который система ищет в хранилище. Например:

SELECT ?person ?name
WHERE {
?person <знать> <Человек456> ;
<имя> ?name .
}

Этот запрос находит всех, кто знает Человека456, и извлекает их имена. Точка с запятой означает продолжение описания свойств того же субъекта.

SPARQL поддерживает:

  • фильтрацию по условиям (FILTER), включая регулярные выражения и сравнения;
  • агрегацию (GROUP BY, COUNT);
  • объединение (UNION);
  • построение новых триплетов (CONSTRUCT);
  • описание вывода в форматах RDF/XML, Turtle, JSON-LD.

Важно: SPARQL не привязан к конкретной СУБД. Он реализован в системах вроде Apache Jena Fuseki, Virtuoso, GraphDB. Его сила — в стандартизации семантических запросов, что позволяет объединять данные из разнородных источников, если они конформны RDF.

Cypher

Cypher — декларативный язык, разработанный для Neo4j. Он визуально близок к ASCII-графике: узлы обозначаются в круглых скобках ( ), рёбра — в квадратных [ ], направление — стрелками -->.

Пример запроса: найти всех друзей друзей пользователя с именем «Тимур»:

MATCH (u:User {name: "Тимур"})-[:FRIEND]->(friend)-[:FRIEND]->(fof)
RETURN fof.name, COUNT(*) AS common_friends
ORDER BY common_friends DESC

Здесь:

  • MATCH задаёт паттерн пути длиной два ребра;
  • [:FRIEND] — тип связи;
  • {name: "Тимур"} — фильтр по свойству узла;
  • RETURN определяет выходной набор.

Cypher поддерживает:

  • переменные длины (-[:FRIEND*1..3]->);
  • условия в WHERE;
  • агрегацию (WITH, COLLECT);
  • создание узлов и рёбер (CREATE, MERGE);
  • подзапросы (через CALL {} в новых версиях).

Особенность Cypher — интуитивность для человека, привыкшего мыслить связями. Он менее формализован, чем SPARQL, но более удобен для разработчиков прикладных систем.

Gremlin

Gremlin — императивный язык обхода графов, входящий в экосистему Apache TinkerPop. Он задаёт как пройти по графу: «начни с узла, пройди по связям FRIEND, отфильтруй по возрасту, собери имена». Gremlin — это DSL, встроенный в JVM-языки (Groovy, Java, Scala), но также доступен через Gremlin Server в виде строковых запросов.

Пример (на Gremlin-Groovy):

g.V().has('User', 'name', 'Тимур')
.out('FRIEND')
.out('FRIEND')
.values('name')
.groupCount()

Здесь:

  • g — граф-объект;
  • V() — поток всех вершин;
  • has() — фильтр;
  • out('FRIEND') — переход по исходящим рёбрам типа FRIEND;
  • values(), groupCount() — терминальные операции.

Gremlin мощен тем, что позволяет:

  • комбинировать обход с вычислениями (например, алгоритм PageRank);
  • писать пользовательские шаги;
  • работать с гибридными структурами (граф + документ).

Он используется в JanusGraph, Amazon Neptune, DSE Graph. Его выбор оправдан, когда требуется гибкость и интеграция с аналитическими фреймворками.

Сравнение подходов

КритерийSPARQLCypherGremlin
Парадигмадекларативный (описание результата)декларативный (паттерн-матчинг)императивный (обход)
ОсноваRDF, семантическая паутинаLabeled Property Graph (Neo4j)Property Graph (TinkerPop)
СтандартизацияW3C Recommendationде-факто стандарт (Neo4j)стандарт экосистемы Apache
Сложность освоениясредняя (требует понимания RDF)низкая (визуальный синтаксис)высокая (логика потока данных)
Типичное применениеинтеграция знаний, open dataприкладные приложения (рекомендации, фрод)аналитика, machine learning на графах

Выбор языка определяется онтологической культурой проекта: если данные моделируются как факты в глобальном пространстве имён (например, Wikidata), SPARQL неизбежен. Если задача — быстро построить MVP с социальными связями — Cypher эффективнее. Для глубокой аналитики — Gremlin.


4. Языки полнотекстового поиска

Полнотекстовый поиск решает задачу, несвойственную реляционным СУБД: находить документы по смысловому содержанию, а не по точному совпадению поля. Здесь возникают понятия релевантности, взвешивания терминов, нормализации текста, фазового поиска.

Основа большинства современных решений — Apache Lucene, библиотека на Java, разработанная Дагом Каттингом. Она предоставляет:

  • инвертированный индекс — структуру, где по каждому термину хранится список документов, его содержащих;
  • анализаторы — цепочки обработки текста (токенизация, приведение к нижнему регистру, стемминг, удаление стоп-слов);
  • алгоритмы ранжирования (TF-IDF, BM25);
  • поддержку синонимов, n-грамм, fuzzy-поиска.

Lucene — это библиотека, а не сервер. Чтобы использовать её, нужны обёртки: Elasticsearch, OpenSearch, Apache Solr. Каждая из них предоставляет собственный язык запросов поверх Lucene.

Lucene Query Syntax

Это строка, интерпретируемая анализатором Lucene. Пример:

title:"полный текст" AND (author:Тагиров OR author:Tagirov) AND year:[2020 TO 2025]

Элементы синтаксиса:

  • Кавычки — фразовый поиск («полный текст» как единая последовательность);
  • ОператорыAND, OR, NOT (регистрозависимо, могут быть &&, ||, !);
  • Поляtitle:, author: — поиск только в указанном поле;
  • Диапазоны[2020 TO 2025] (включительно), {2020 TO 2025} (исключительно);
  • Префиксы и вайлдкардыdoc*, t?mur;
  • Fuzzy-поисктермин~2 (редакционное расстояние до 2);
  • Boostingважный^5 (повышение веса термина).

Этот синтаксис используется в Solr и как fallback в Elasticsearch (через параметр q). Он прост, но ограничен: нельзя задавать сложные условия (вложенные bool-выражения, функциональное ранжирование), нет поддержки агрегаций.

Elasticsearch Query DSL

DSL (Domain Specific Language) в Elasticsearch — это JSON-структура, описывающая запрос. Он декларативен, строго типизирован и охватывает все возможности движка.

Основные типы запросов:

  • match — полнотекстовый поиск с анализом: разбивает запрос на термины, ищет по инвертированному индексу, ранжирует.
  • term — точное совпадение без анализа (для keyword-полей).
  • bool — комбинирование подзапросов с помощью must, should, must_not, filter.
  • range, prefix, wildcard — структурные условия.
  • nested, has_child — работа со вложенными объектами.
  • function_score — кастомное ранжирование (например, усилить свежие документы или близкие по геолокации).

Пример: найти статьи по «языкам запросов», написанные за последние два года, исключая черновики, с приоритетом на те, где термин встречается в заголовке:

{
"query": {
"bool": {
"must": [
{ "match": { "content": "языки запросов" } }
],
"should": [
{ "match": { "title": { "query": "языки запросов", "boost": 2.0 } } }
],
"filter": [
{ "range": { "published_at": { "gte": "2023-01-01" } } },
{ "term": { "status": "published" } }
]
}
}
}

Ключевые особенности DSL:

  • разделение на запрос и фильтр: фильтры кэшируются и не влияют на релевантность (score), запросы — влияют;
  • поддержка агрегаций (aggs) — аналитика поверх результатов: гистограммы, терм-облака, топ-N;
  • поддержка геопоиска, подсветки совпадений, объяснения (_explain).

OpenSearch, форк Elasticsearch после изменения лицензии, сохраняет совместимость с DSL и постепенно добавляет расширения (например, SQL-интерфейс через плагин).

Solr Query Syntax и JSON Request API

Apache Solr традиционно использовал строковый запрос (аналог Lucene Syntax), но сейчас активно развивает JSON Request API — альтернативу DSL Elasticsearch. В нём запрос и параметры (смещение, лимит, сортировка) передаются в одном JSON-объекте, что упрощает клиентскую интеграцию.

Оба подхода (Elasticsearch и Solr) сходятся к единому принципу: полнотекстовый запрос — это конфигурируемый пайплайн обработки, включающий анализ, поиск, ранжирование, постпроцессинг. Язык запросов — интерфейс к этому пайплайну.


5. Федеративные и унифицированные подходы к запросам

С увеличением разнообразия хранилищ в одной системе (реляционные таблицы, документы, графы, потоки событий) возникла потребность в инструментах, способных выполнять запросы поперёк границ отдельных СУБД. Это направление получило название федеративный запрос (federated query) или полиглотный запрос (polyglot query).

Проблема не в технической возможности соединить данные — её решают ETL-процессы и middleware. Проблема — в языке: как выразить запрос так, чтобы он был один, а выполнение — распределено по разным движкам, с учётом их особенностей.

Apache Calcite

Apache Calcite — фреймворк для построения систем управления данными. Его ядро — оптимизатор запросов, способный работать с произвольными источниками данных через адаптеры.

Calcite принимает SQL (или его расширение) как входной язык и:

  • парсит и валидирует запрос;
  • строит логический и физический план выполнения;
  • распределяет подзапросы по адаптерам (например, часть — в PostgreSQL, часть — в Elasticsearch);
  • собирает и объединяет результаты;
  • при необходимости — переносит вычисления на сторону клиента (например, JOIN между разными источниками).

Адаптеры существуют для:

  • реляционных СУБД (через JDBC);
  • NoSQL-систем (Cassandra, MongoDB);
  • файловых форматов (Parquet, CSV);
  • REST API и даже Java-коллекций.

Пример использования — Apache Drill и Dremio: они предоставляют единый SQL-интерфейс к разнородным данным без предварительной загрузки (schema-on-read). Пользователь пишет SELECT user.name, log.action FROM users JOIN logs ON users.id = logs.user_id, а Calcite определяет, какие условия могут быть выполнены на стороне каждого источника, а где потребуется materialization.

Сила Calcite — в декларативности и переносимости. Он позволяет построить поверх себя систему, соответствующую требованиям: от лёгкого враппера до полноценного data warehouse.

GraphQL как язык оркестрации данных

GraphQL, изначально созданный Facebook для API фронтендам, всё чаще рассматривается как язык интеграции на стороне бэкенда. Его ключевая особенность — запрос описывает структуру ожидаемого ответа. Клиент указывает, какие поля нужны, и в каком виде. Сервер решает, откуда их брать: из одной СУБД, нескольких, кэша или внешнего API.

Например:

query {
user(id: "123") {
name
posts(first: 5) {
title
comments {
author { name }
likes
}
}
friends {
name
mutualFriendsCount
}
}
}

Запрос может быть реализован так:

  • name, friends — из Neo4j (граф социальных связей);
  • posts, comments — из PostgreSQL (реляционная часть);
  • mutualFriendsCount — вычисляется на лету через Gremlin-скрипт;
  • likes — из Redis (счётчик в памяти).

Реализуется это через resolvers — функции, сопоставленные каждому полю. Они могут быть асинхронными, кэшироваться, объединяться в batch-запросы (DataLoader). Таким образом, GraphQL становится языком оркестрации, а не хранения. Он не заменяет SQL или SPARQL, но инкапсулирует их, предоставляя единый интерфейс.

Важное ограничение: GraphQL не поддерживает агрегации и сложные условия фильтрации «из коробки». Но расширения вроде GraphQL Cop или Hasura добавляют агрегаты (_count, _avg), пагинацию, full-text search — по сути, проецируя возможности SQL на GraphQL-модель.

SQL/MED и внешние таблицы

SQL/MED (Management of External Data) — часть стандарта SQL (ISO/IEC 9075-9), введённая в 2003 году и расширенная в 2016. Она определяет механизмы:

  • объявления внешнего сервера (foreign server);
  • создания внешней таблицы (foreign table) — виртуальной таблицы, данные для которой извлекаются из внешнего источника;
  • указания отображения (mapping) между локальной и внешней схемой.

PostgreSQL реализует это через расширение postgres_fdw (Foreign Data Wrapper). Пример:

CREATE FOREIGN TABLE logs_es (
id text,
message text,
timestamp timestamptz
)
SERVER elasticsearch_server
OPTIONS (index 'application-logs');

После этого можно писать:

SELECT u.name, l.message
FROM users u
JOIN logs_es l ON u.id = l.user_id
WHERE l.timestamp > '2025-11-01';

Система перенесёт фильтрацию по timestamp в Elasticsearch, а JOIN — в локальную память или на сторону PostgreSQL, в зависимости от статистики и настроек. Таким образом, SQL остаётся единым языком, даже когда данные физически разнесены.

Этот подход не универсален: он требует, чтобы внешняя система поддерживала протокол (чаще всего — через ODBC/JDBC или кастомный FDW), и не решает проблему транзакционной согласованности. Но он минимизирует дублирование кода и упрощает миграцию.


6. Языки запросов в контексте архитектурных паттернов

Выбор языка запросов не происходит в вакууме — он обусловлен архитектурными решениями системы. Ниже — влияние трёх ключевых паттернов.

Polyglot Persistence

Термин, введённый Мартином Фаулером, означает использование разных моделей данных и СУБД в рамках одного приложения, исходя из характера задачи. Пример:

  • пользовательские профили — в PostgreSQL (ACID, связи);
  • события пользователя — в Apache Kafka (потоковая обработка);
  • аналитические отчёты — в ClickHouse (колоночное хранение, агрегация);
  • рекомендации — в Neo4j (граф связей);
  • логи и метрики — в Elasticsearch (полнотекст, временные ряды).

В такой системе нет единого языка запросов. Вместо этого появляется языковая среда, где:

  • разработчик выбирает инструмент под задачу;
  • данные синхронизируются через CDC (Change Data Capture) или event sourcing;
  • клиентский код работает с абстракциями (репозиториями), скрывающими детали.

Преимущество — максимальная эффективность каждой подсистемы. Риск — фрагментация знаний и сложность отладки «сквозных» запросов.

CQRS (Command Query Responsibility Segregation)

CQRS разделяет операции на:

  • команды (изменение состояния — INSERT, UPDATE);
  • запросы (чтение — SELECT).

При этом модель записи и модель чтения могут быть физически разными. Язык команд часто близок к доменному (DDD), а язык запросов — оптимизирован под потребителя.

Пример:

  • команда PlaceOrder сохраняется в реляционной БД как событие;
  • проекция этого события создаёт документ в MongoDB вида { orderId, customerName, total, status };
  • фронтенд читает через db.orders.find({ customerName: "Тимур" }).

Здесь язык запросов — MongoDB Query Language — выбирается из-за формы отчёта. Это позволяет:

  • денормализовать данные под конкретный интерфейс;
  • использовать специализированные индексы;
  • изолировать нагрузку чтения от записи.

CQRS делает язык запросов зависимым от представления, а не от источника. Это смещает фокус с «как хранится» на «как потребляется».

Event Sourcing

В event sourcing состояние агрегата не хранится напрямую — только последовательность событий (UserCreated, EmailChanged, OrderPlaced). Текущее состояние вычисляется путём репликации (replay) событий.

Запросы к таким данным делятся на два класса:

  1. Текущее состояние — получается через проекции (materialized views), которые обновляются асинхронно при поступлении событий. Язык запросов к проекциям — стандартный для выбранного хранилища (SQL, CQL и т.д.).

  2. Аналитика по событиям — требует языков, поддерживающих оконные функции, сессионные окна, паттерн-матчинг. Здесь доминируют:

    • SQL:2011+ с OVER(), MATCH_RECOGNIZE;
    • Flink SQL, ksqlDB — для потоковых запросов;
    • Spark SQL — для пакетной обработки.

Например, в ksqlDB можно написать:

SELECT user_id, COUNT(*) AS login_count
FROM logins
WINDOW TUMBLING (SIZE 1 HOUR)
GROUP BY user_id
HAVING COUNT(*) > 10;

Этот запрос обнаружит пользователей с более чем 10 входами в час — в реальном времени. Язык здесь не статичен: он описывает динамическое условие, активируемое потоком событий.

Event sourcing показывает: язык запросов становится временнЫм — он оперирует изменениями. Это фундаментальный сдвиг в парадигме.


7. Перспективы

Естественно-языковые интерфейсы (NLQ)

Natural Language Query — попытка заменить синтаксис языка запросов привычной речью: «Покажи мне всех пользователей из Уфы, которые заходили на сайт в ноябре и не совершали покупок».

Современные NLQ-системы (например, в Tableau, Power BI, ThoughtSpot) работают по схеме:

  1. парсинг фразы с помощью NLP-моделей (часто fine-tuned BERT);
  2. сопоставление терминов с метаданными (таблицами, полями, синонимами);
  3. генерация SQL/DQL-запроса;
  4. выполнение и возврат результата.

Ограничения:

  • неоднозначность языка («покажи активных» — что значит «активных»?);
  • необходимость точной онтологии (data dictionary);
  • сложность отладки сгенерированного запроса.

NLQ не отменяет язык запросов — он становится компилятором «с естественного на запросный». Профессионал всё равно должен понимать, что сгенерировано, и корректировать.

Генерация запросов с помощью LLM

Large Language Models (вроде вас и меня, но специализированные — CodeLlama, StarCoder) могут:

  • писать SQL по описанию;
  • переводить запрос с одного диалекта на другой (T-SQL → PostgreSQL);
  • объяснять, что делает сложный запрос;
  • находить узкие места в плане выполнения.

Ключевой вызов — детерминированность и безопасность. LLM может:

  • придумать несуществующие функции;
  • пропустить условия фильтрации (утечка данных);
  • использовать уязвимый синтаксис (SQL injection в сгенерированной строке).

Поэтому промышленные решения (GitHub Copilot for SQL, Amazon CodeWhisperer) включают:

  • пост-валидацию через парсер СУБД;
  • sandbox-выполнение;
  • ограничение по scope (только SELECT, без DDL/DCL).

LLM — усилитель, который сокращает время на рутину, но требует от специалиста понимания корректности результата.

Безопасность на уровне запроса

Современные СУБД внедряют механизмы, ограничивающие доступ внутри запроса:

  • Row-Level Security (RLS) — PostgreSQL, SQL Server: политики, добавляющие неявные условия WHERE tenant_id = current_user_tenant();
  • Column Masking — скрытие значений (например, ***-**-4567 для ИНН) на уровне вывода;
  • Dynamic Data Masking — в реальном времени при чтении;
  • Attribute-Based Access Control (ABAC) — в OpenSearch, Elasticsearch: права зависят от тегов документа и атрибутов пользователя.

Эти механизмы интегрированы в язык запросов: разработчик пишет один запрос, а система добавляет ограничения автоматически. Это смещает ответственность за безопасность с приложения на СУБД — и требует понимания того, как политики влияют на план выполнения.

Стандартизация

Несмотря на разнообразие, идёт сближение:

  • SQL остаётся lingua franca: даже Elasticsearch добавил SQL-интерфейс, MongoDB — Atlas SQL;
  • JSON становится универсальным форматом обмена: SQL/JSON (стандарт), JSON_TABLE в Oracle/MySQL;
  • Graph Query Language (GQL) находится в стадии стандартизации ISO — попытка создать унифицированный язык для property graphs, по аналогии с SQL для реляций.

Стандартизация не убивает разнообразие — она создаёт мосты. Как HTTP не отменил TCP, но позволил разным протоколам работать поверх одного транспорта.